{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    },
    "gpuClass": "standard",
    "accelerator": "GPU"
  },
  "cells": [
    {
      "cell_type": "markdown",
      "source": [
        "# Run txtai in native code\n",
        "\n",
        "txtai currently has two main methods of execution: Python or via a HTTP API. There are API bindings for [JavaScript](https://github.com/neuml/txtai.js), [Java](https://github.com/neuml/txtai.java), [Rust](https://github.com/neuml/txtai.rs) and [Go](https://github.com/neuml/txtai.go).\n",
        "\n",
        "This notebook presents a way to run txtai as part of a native executable with the [Python C API](https://docs.python.org/3/c-api/index.html). We'll run an example in C and even call txtai from assembly code!\n",
        "\n",
        "Before diving into this notebook, it's important to emphasize that connecting to txtai via the HTTP API has a number of major advantages. This includes decoupling from Python, the ability to offload txtai to a different machine and scaling with cloud compute. With that being said, this notebook demonstrates an additional way to integrate txtai along with providing an informative and perhaps academic programming exercise."
      ],
      "metadata": {
        "id": "-xU9P9iSR-Cy"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "# Install dependencies\n",
        "\n",
        "Install `txtai` and all dependencies."
      ],
      "metadata": {
        "id": "shlUi2kKS7KT"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "xEvX9vCpn4E0"
      },
      "outputs": [],
      "source": [
        "%%capture\n",
        "!pip install git+https://github.com/neuml/txtai#egg=txtai[pipeline] sacremoses\n",
        "\n",
        "# Remove tensorflow as it's not used and prints noisy log messages\n",
        "!pip uninstall -y tensorflow\n",
        "\n",
        "# Install python3.7-dev and nasm\n",
        "!apt-get install python3.7-dev nasm"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# Workflow configuration\n",
        "\n",
        "This configuration builds a workflow to translate input text to French. More information on workflows can be found in [txtai's documentation](https://neuml.github.io/txtai/workflow).\n"
      ],
      "metadata": {
        "id": "AtEdP7Utw3mk"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "%%writefile config.yml\n",
        "summary:\n",
        "  path: sshleifer/distilbart-cnn-12-6\n",
        "\n",
        "textractor:\n",
        "  join: true\n",
        "  lines: false\n",
        "  minlength: 100\n",
        "  paragraphs: true\n",
        "  sentences: false\n",
        "  tika: false\n",
        "\n",
        "translation:\n",
        "\n",
        "workflow:\n",
        "  summary:\n",
        "    tasks:\n",
        "    - action: textractor\n",
        "      task: url\n",
        "    - action: summary\n",
        "\n",
        "  translate:\n",
        "    tasks:\n",
        "    - action: translation\n",
        "      args: \n",
        "      - fr"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "DPWrubv5oOn7",
        "outputId": "bdfba131-c36e-421d-8c17-a726be4615d2"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Writing config.yml\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# Python C API\n",
        "\n",
        "Next we'll build an interface to txtai workflows with the Python C API. This logic will load Python, create a txtai application instance and add methods to run workflows.\n",
        "\n",
        "Some assumptions are made:\n",
        "\n",
        "- txtai is installed and available\n",
        "- A workflow is available in a file named `config.yml`\n",
        "- The workflow only returns the first element\n",
        "\n",
        "These assumptions are for brevity. This example could be expanded on and built into a more robust, full-fledged library.\n",
        "\n",
        "While this example is in C, Rust has a well-maintained and popular library for interfacing with Python, [PyO3](https://github.com/PyO3/pyo3). Interfacing with the Python C API is also possible in Java, JavaScript and Go but not as straighforward."
      ],
      "metadata": {
        "id": "KIg_QDVRo-6-"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "%%writefile workflow.c\n",
        "#include <Python.h>\n",
        "\n",
        "// Global instances\n",
        "PyObject *module = NULL, *app = NULL;\n",
        "\n",
        "/**\n",
        " * Create txtai module.\n",
        " */\n",
        "PyObject* txtai() {\n",
        "    PyObject* module = NULL;\n",
        "    module = PyImport_ImportModule(\"txtai.app\");\n",
        "    return module;\n",
        "}\n",
        "\n",
        "/**\n",
        " * Create txtai application instance.\n",
        " */\n",
        "PyObject* application() {\n",
        "    PyObject* app = NULL;\n",
        "    app = PyObject_CallMethod(module, \"Application\", \"z\", \"config.yml\");\n",
        "    return app;\n",
        "}\n",
        "\n",
        "/**\n",
        " * Run txtai workflow.\n",
        " */\n",
        "PyObject* run(char** args) {\n",
        "    PyObject* result = NULL;\n",
        "    result = PyObject_CallMethod(app, \"workflow\", \"z[z]\", args[0], args[1]);\n",
        "    return result;\n",
        "}\n",
        "\n",
        "/**\n",
        " * Cleanup Python objects.\n",
        " */\n",
        "void cleanup() {\n",
        "    // Ensure Python instance exists\n",
        "    if (Py_IsInitialized()) {\n",
        "        PyErr_Print();\n",
        "\n",
        "        Py_CLEAR(app);\n",
        "        Py_CLEAR(module);\n",
        "\n",
        "        Py_FinalizeEx();\n",
        "    }\n",
        "}\n",
        "\n",
        "/**\n",
        " * Initialize a txtai application and run a workflow.\n",
        " */\n",
        "const char* workflow(char** args) {\n",
        "    PyObject* result = NULL;\n",
        "\n",
        "    // Create application instance if it doesn't already exist\n",
        "    if (!Py_IsInitialized()) {\n",
        "        // Start Python Interpreter\n",
        "        Py_Initialize();\n",
        "\n",
        "        // Create txtai module\n",
        "        module = txtai();\n",
        "\n",
        "        // Handle errors\n",
        "        if (!module) {\n",
        "            cleanup();\n",
        "            return NULL;\n",
        "        }\n",
        "\n",
        "        // Create txtai application\n",
        "        app = application();\n",
        "\n",
        "        // Handle errors\n",
        "        if (!app) {\n",
        "            cleanup();\n",
        "            return NULL;\n",
        "        }\n",
        "    }\n",
        "\n",
        "    // Run workflow\n",
        "    result = run(args);\n",
        "\n",
        "    // Handle errors\n",
        "    if (!result) {\n",
        "        cleanup();\n",
        "        return NULL;\n",
        "    }\n",
        "\n",
        "    // Get first result\n",
        "    const char *text = PyUnicode_AsUTF8(PyIter_Next(result));\n",
        "\n",
        "    // Cleanup result\n",
        "    Py_CLEAR(result);\n",
        "\n",
        "    return text;\n",
        "}"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "rv64SWDkpmIx",
        "outputId": "1c19ae3c-7f06-43ac-8638-37ee7b5e4eab"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Writing workflow.c\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# Run txtai workflow in C\n",
        "\n",
        "Let's now write a C program to run a workflow using command line arguments as input. "
      ],
      "metadata": {
        "id": "K19FZK5StVbf"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "%%writefile main.c\n",
        "#include <stdio.h>\n",
        "\n",
        "extern char* workflow(char** argv);\n",
        "extern void cleanup();\n",
        "\n",
        "/**\n",
        " * Run a txtai workflow and print results.\n",
        " */\n",
        "int main(int argc, char** argv) {\n",
        "    if (argc < 3) {\n",
        "        printf(\"Usage: workflow <name> <element>\\n\");\n",
        "        return 1;\n",
        "    }\n",
        "\n",
        "    // Run workflow using command line arguments\n",
        "    char* text = workflow(argv + 1);\n",
        "    if (text) {\n",
        "        printf(\"%s\\n\", text);\n",
        "    }\n",
        "\n",
        "    // Cleanup\n",
        "    cleanup();\n",
        "\n",
        "    return 0;\n",
        "}"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "apBnMta8pepD",
        "outputId": "e6573cec-6b92-424c-e594-aa46c7cd18b6"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Writing main.c\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# Compile and run\n",
        "\n",
        "Time to compile this all into an executable and run!"
      ],
      "metadata": {
        "id": "5kVxaL0FuOgu"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "!cc -c main.c -I/usr/include/python3.7m\n",
        "!cc -c workflow.c -I/usr/include/python3.7m\n",
        "!cc -o workflow workflow.o main.o -lpython3.7m"
      ],
      "metadata": {
        "id": "KjQYctCqp1Vi"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "!./workflow translate \"I'm running machine translation using a transformers model in C!\""
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "t5IWOF8ap-a7",
        "outputId": "071f3d26-955e-4e10-cce4-5ed9774960d5"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "J'exécute la traduction automatique à l'aide d'un modèle de transformateurs en C!\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "And there it is, a translation workflow from English to French in a native executable, all backed by Transformers models. Any workflow YAML can be loaded and run in C using this method, which is pretty powerful.\n",
        "\n",
        "Embedding txtai in native executable adds libpython as a dependency (libraries from 3rd party modules such as PyTorch and NumPy also load dynamically). See output of ldd below.\n",
        "This opens up an avenue to embed txtai in native code provided it is acceptable to add libpython as a project dependency. \n",
        "\n",
        "As mentioned above, connecting to a txtai HTTP API instance is a less tightly coupled way to accomplish the same thing."
      ],
      "metadata": {
        "id": "4eNk20mCxp3r"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "!ldd workflow | grep python"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "SLq5ix5mGPAb",
        "outputId": "a46720e6-c721-4df5-92a7-a1bd897511d0"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\tlibpython3.7m.so.1.0 => /usr/lib/x86_64-linux-gnu/libpython3.7m.so.1.0 (0x00007efcba85e000)\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# Machine learning in Assembly?\n",
        "\n",
        "Now for a more academic exercise perhaps bringing you back to a computer organization/logic class from college. Let's see if we can run the same program in assembly!"
      ],
      "metadata": {
        "id": "n6E-yHIjy0UY"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "%%writefile main.asm\n",
        "global main\n",
        "\n",
        "; External C library functions\n",
        "extern puts\n",
        "\n",
        "; External txtai functions\n",
        "extern workflow, cleanup\n",
        "\n",
        "; Default to REL mode\n",
        "default REL\n",
        "\n",
        "section .data\n",
        "    message:    db  \"Usage: workflow <name> <element>\", 0\n",
        "\n",
        "section .text\n",
        "\n",
        "; Print a usage message\n",
        "usage:\n",
        "    mov     rdi, message\n",
        "    call    puts\n",
        "    jmp     done\n",
        "\n",
        "; Main function\n",
        "main:\n",
        "    ; Enter\n",
        "    sub     rsp, 8\n",
        "\n",
        "    ; Read argc - require workflow name and element (plus program name)\n",
        "    cmp     rdi, 3\n",
        "    jl      usage\n",
        "\n",
        "    ; Run txtai workflow with argv params (skip program name) and print result\n",
        "    lea     rdi, [rsi + 8]\n",
        "    call    workflow\n",
        "    mov     rdi, rax\n",
        "    call    puts\n",
        "\n",
        "done:\n",
        "    ; Close txtai application instance\n",
        "    call    cleanup\n",
        "\n",
        "    ; Exit\n",
        "    add     rsp, 8\n",
        "    ret"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "osrjXJonqcTm",
        "outputId": "b2d662d0-2a71-4b53-a956-c9277c4a7d2b"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Writing main.asm\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Build workflow executable\n",
        "!nasm -felf64 main.asm\n",
        "!cc -c workflow.c -I/usr/include/python3.7m\n",
        "!cc -o workflow -no-pie workflow.o main.o -lpython3.7m"
      ],
      "metadata": {
        "id": "sQMEx0LFq80Z"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "!./workflow translate \"I'm running machine translation using a transformers model with assembler!\""
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "YG6faU7HrCrH",
        "outputId": "a7a77cbf-5806-4241-cd67-03e79438e6f2"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "J'exécute la traduction automatique à l'aide d'un modèle de transformateurs avec assembleur!\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "Just as before, the input text is translated to French using a machine translation model. But this time the code executing the logic was in assembly!\n",
        "\n",
        "Probably not terribly useful but using the lowest level of code possible proves that any higher-level native code can do the same. "
      ],
      "metadata": {
        "id": "SNXAqFm6bNSw"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "# Multiple workflow calls\n",
        "\n",
        "Everything up to this point has been a single workflow call. Much of the run time is spent on loading models as part of the txtai workflow. The next example will run a series of workflow calls and compare how long it takes vs a single workflow command line call. Once again in assembly."
      ],
      "metadata": {
        "id": "dB_5UWoDH0nY"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "%%writefile main.asm\n",
        "global main\n",
        "\n",
        "; External C library functions\n",
        "extern printf\n",
        "\n",
        "; External txtai functions\n",
        "extern workflow, cleanup\n",
        "\n",
        "; Default to REL mode\n",
        "default REL\n",
        "\n",
        "section .data\n",
        "    format:     db  \"action: %s\", 10, \"input:  %s\", 10, \"output: %s\", 10, 10, 0\n",
        "    summary:    db  \"summary\", 0\n",
        "    translate:  db  \"translate\", 0\n",
        "    text1:      db  \"txtai executes machine-learning workflows to transform data and build AI-powered semantic search applications.\", 0\n",
        "    text2:      db  \"Traditional search systems use keywords to find data\", 0\n",
        "    url1:       db  \"https://github.com/neuml/txtai\", 0\n",
        "    url2:       db  \"https://github.com/neuml/paperai\", 0\n",
        "\n",
        "section .text\n",
        "\n",
        "; Run txtai workflow and print results\n",
        "%macro txtai 2\n",
        "    ; Workflow name and element\n",
        "    push    %2\n",
        "    push    %1\n",
        "\n",
        "    ; Run workflow\n",
        "    lea     rdi, [rsp]\n",
        "    call    workflow\n",
        "\n",
        "    ; Print action-input-output\n",
        "    mov     rdi, format\n",
        "    mov     rsi, [rsp]\n",
        "    mov     rdx, [rsp + 8]\n",
        "    mov     rcx, rax\n",
        "    call    printf\n",
        "\n",
        "    ; Restore stack\n",
        "    add     rsp, 16\n",
        "%endmacro\n",
        "\n",
        "; Main function\n",
        "main:\n",
        "    ; Enter\n",
        "    sub     rsp, 8\n",
        "\n",
        "    ; Run workflows\n",
        "    txtai   translate, text1\t\n",
        "    txtai   translate, text2\n",
        "    txtai   summary, url1\n",
        "    txtai   summary, url2\n",
        "\n",
        "done:\n",
        "    ; Close txtai application instance\n",
        "    call    cleanup\n",
        "\n",
        "    ; Exit\n",
        "    add     rsp, 8\n",
        "    ret"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "wzTYCSaqCSZu",
        "outputId": "1f48015f-7654-4d86-9f2a-2e462c22f4e6"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Overwriting main.asm\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "!time ./workflow translate \"I'm running machine translation using a transformers model with assembler!\""
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "BSmdBuPhIIbw",
        "outputId": "b45c1ea5-e0fb-4331-f033-5370cdc2ead0"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "J'exécute la traduction automatique à l'aide d'un modèle de transformateurs avec assembleur!\n",
            "\n",
            "real\t0m19.208s\n",
            "user\t0m11.256s\n",
            "sys\t0m3.224s\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Build workflow executable\n",
        "!nasm -felf64 main.asm\n",
        "!cc -c workflow.c -I/usr/include/python3.7m\n",
        "!cc -no-pie -o workflow workflow.o main.o -lpython3.7m"
      ],
      "metadata": {
        "id": "HoRsPeorC5rC"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "!time ./workflow"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "7Da6TZlDC8Mj",
        "outputId": "e2b46994-62d1-42d1-ab77-0d690a7d7d67"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "action: translate\n",
            "input:  txtai executes machine-learning workflows to transform data and build AI-powered semantic search applications.\n",
            "output: txtai exécute des workflows d'apprentissage automatique pour transformer les données et construire des applications de recherche sémantique alimentées par l'IA.\n",
            "\n",
            "action: translate\n",
            "input:  Traditional search systems use keywords to find data\n",
            "output: Les systèmes de recherche traditionnels utilisent des mots-clés pour trouver des données\n",
            "\n",
            "action: summary\n",
            "input:  https://github.com/neuml/txtai\n",
            "output: txtai executes machine-learning workflows to transform data and build AI-powered semantic search applications. Semantic search applications have an understanding of natural language and identify results that have the same meaning, not necessarily the same keywords. API bindings for JavaScript, Java, Rust and Go. Cloud-native architecture scales out with container orchestration systems (e. g. Kubernetes)\n",
            "\n",
            "action: summary\n",
            "input:  https://github.com/neuml/paperai\n",
            "output: paperai is an AI-powered literature discovery and review engine for medical/scientific papers. Paperai was used to analyze the COVID-19 Open Research Dataset (CORD-19) paperai and NeuML have been recognized in the following articles: Cord-19 Kaggle Challenge Awards Machine-Learning Experts Delve Into 47,000 Papers on Coronavirus Family.\n",
            "\n",
            "\n",
            "real\t0m22.478s\n",
            "user\t0m13.776s\n",
            "sys\t0m3.218s\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "As we can see, running 4 workflow actions is about the same runtime as a single action when accounting for model load times."
      ],
      "metadata": {
        "id": "zNalTEg5cmhX"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "# Wrapping up\n",
        "\n",
        "This notebook walked through an example on how to run txtai with native code. While the HTTP API is a better route to go, this is another way to work with txtai!"
      ],
      "metadata": {
        "id": "4L8smyyXc8q8"
      }
    }
  ]
}